﻿ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Concurrent;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using PServiceBus.Core.Logger;

namespace PServiceBus.Core.Runtime {
    public class DependencyFinder<TTypeImp> where TTypeImp : class {
        static ConcurrentDictionary<string, Type> _types = new ConcurrentDictionary<string, Type>();

        /// <summary>
        /// Find a type that implement the specified TTypeImp
        /// </summary>
        /// <param name="name"></param>
        /// <param name="searchPath"></param>
        /// <returns></returns>
        public Type Find(string name, string searchPath = ".") {
            var impType = typeof(TTypeImp);
            name = name.ToLower();
            var key = String.Format("{0}.{1}", typeof(TTypeImp).FullName, name);
            return _types.GetOrAdd(key, o =>
            {
                searchPath = searchPath ?? ".";
                var type = default(Type);
                Compose(searchPath);

                var types = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a =>
                    a.GetTypes().Where(t => impType.IsAssignableFrom(t) && t != impType));
                foreach (var t in types) {
                    var impTypeName = t.Name.ToLower();
                    if (impTypeName == name)
                        type = t;
                    else _types.TryAdd(impTypeName, t);
                }

                return type;
            });
        }

        /// <summary>
        /// Get all types that implement the specified TTypeImpl
        /// </summary>
        /// <param name="searchPath"></param>
        /// <returns></returns>
        public IEnumerable<Type> GetTypes(string searchPath = ".") {
            var impType = typeof(TTypeImp);
            Compose(searchPath);
            var types = new List<Type>();
            var asms = AppDomain.CurrentDomain.GetAssemblies();
            foreach (var asm in asms) {
                MethodHelper.Try(() => {
                    var asmTypes = asm.GetTypes();
                    types.AddRange(asmTypes.Where(t => impType.IsAssignableFrom(t) && t != impType));
                });
            }
            return types;
        }

        /// <summary>
        /// Load type into memory for later use
        /// </summary>
        /// <param name="searchPath"></param>
        private static void Compose(string searchPath) {

            var catalog = new AggregateCatalog();
            var asm = typeof(DependencyFinder<>).Assembly;
            catalog.Catalogs.Add(new AssemblyCatalog(asm));
            var folders = searchPath.Split(';');
            foreach (var folder in folders)
                catalog.Catalogs.Add(new DirectoryCatalog(folder));

            var container = new CompositionContainer(catalog);

            container.ComposeParts();
        }
    }
}
